породы в БД
Gitea Actions Demo / build_and_push (push) Has been cancelled
Details
Gitea Actions Demo / build_and_push (push) Has been cancelled
Details
This commit is contained in:
parent
ce5c715611
commit
e121c6af34
|
|
@ -0,0 +1,84 @@
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def copy_convert_to_webp(
|
||||||
|
source_dir,
|
||||||
|
dest_dir,
|
||||||
|
samples_per_folder=5,
|
||||||
|
size_threshold=1024 * 1024,
|
||||||
|
quality_high=90,
|
||||||
|
quality_low=70,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Копирует структуру папок и конвертирует изображения в WebP
|
||||||
|
:param source_dir: Исходная директория с изображениями
|
||||||
|
:param dest_dir: Целевая директория для копирования
|
||||||
|
:param samples_per_folder: Количество изображений для выбора из каждой папки
|
||||||
|
:param size_threshold: Пороговый размер файла (в байтах) для сильного сжатия
|
||||||
|
:param quality_high: Качество для маленьких изображений (0-100)
|
||||||
|
:param quality_low: Качество для больших изображений (0-100)
|
||||||
|
"""
|
||||||
|
image_extensions = (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp")
|
||||||
|
|
||||||
|
def is_image_file(filename):
|
||||||
|
return filename.lower().endswith(image_extensions)
|
||||||
|
|
||||||
|
def convert_to_webp(src_path, dest_path, quality):
|
||||||
|
try:
|
||||||
|
with Image.open(src_path) as img:
|
||||||
|
# Сохраняем прозрачность для RGBA
|
||||||
|
if img.mode in ("RGBA", "LA"):
|
||||||
|
img = img.convert("RGBA")
|
||||||
|
img.save(dest_path, "WEBP", quality=quality, lossless=False, method=6)
|
||||||
|
else:
|
||||||
|
# Конвертируем в RGB для JPEG-подобных изображений
|
||||||
|
img = img.convert("RGB")
|
||||||
|
img.save(dest_path, "WEBP", quality=quality, method=6)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error converting {src_path}: {e}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Создаем корневую целевую папку
|
||||||
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(source_dir):
|
||||||
|
rel_path = os.path.relpath(root, source_dir)
|
||||||
|
target_dir = os.path.join(dest_dir, rel_path)
|
||||||
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
|
images = [f for f in files if is_image_file(f)]
|
||||||
|
if not images:
|
||||||
|
continue
|
||||||
|
|
||||||
|
selected = random.sample(images, min(samples_per_folder, len(images)))
|
||||||
|
|
||||||
|
for file in selected:
|
||||||
|
src_path = os.path.join(root, file)
|
||||||
|
base_name = os.path.splitext(file)[0]
|
||||||
|
dest_path = os.path.join(target_dir, f"{base_name}.webp")
|
||||||
|
|
||||||
|
# Определяем качество на основе размера
|
||||||
|
file_size = os.path.getsize(src_path)
|
||||||
|
quality = quality_low if file_size > size_threshold else quality_high
|
||||||
|
|
||||||
|
# Конвертируем в WebP
|
||||||
|
if not convert_to_webp(src_path, dest_path, quality):
|
||||||
|
# Если ошибка конвертации - копируем оригинал
|
||||||
|
shutil.copy2(src_path, dest_path)
|
||||||
|
print(f"Copied original file instead: {file}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
copy_convert_to_webp(
|
||||||
|
source_dir="assets",
|
||||||
|
dest_dir="webp_output",
|
||||||
|
samples_per_folder=5,
|
||||||
|
size_threshold=1 * 1024 * 20, # 1 MB
|
||||||
|
quality_high=70,
|
||||||
|
quality_low=70,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
import_beerds_sql.py
|
||||||
|
|
||||||
|
Скрипт читает файлы из каталогов:
|
||||||
|
- server/modules/descriptions/repository/breed_descriptions
|
||||||
|
- server/modules/descriptions/repository/breed_signs
|
||||||
|
|
||||||
|
и вместо непосредственной вставки генерирует PostgreSQL‑SQL‑файл, который
|
||||||
|
можно применить через `psql -f <имя_файла.sql>`:
|
||||||
|
|
||||||
|
CREATE TABLE beerds.beerds (
|
||||||
|
id varchar NOT NULL,
|
||||||
|
name text NOT NULL,
|
||||||
|
descriptions text NOT NULL,
|
||||||
|
signs json NOT NULL,
|
||||||
|
alias text NOT NULL,
|
||||||
|
CONSTRAINT beerds_pkey PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2️⃣ Путь к каталогам (можно изменить под свой проект)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
|
||||||
|
DESC_DIR = BASE_DIR / "server/modules/descriptions/repository/breed_descriptions"
|
||||||
|
SIGN_DIR = BASE_DIR / "server/modules/descriptions/repository/breed_signs"
|
||||||
|
print(f"DESC_DIR = {DESC_DIR}")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3️⃣ Функции чтения файлов
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def read_text_file(path: pathlib.Path) -> str:
|
||||||
|
"""Возвращает содержимое текстового файла (unicode)."""
|
||||||
|
with path.open("r", encoding="utf-8") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def read_json_file(path: pathlib.Path) -> dict:
|
||||||
|
"""Возвращает JSON‑объект из файла."""
|
||||||
|
with path.open("r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4️⃣ Формируем словарь: имя → (описание, сигналы)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def build_breed_map(desc_dir: pathlib.Path, sign_dir: pathlib.Path) -> dict:
|
||||||
|
"""Возвращает dict: stem → (description, signs)"""
|
||||||
|
desc_files = {p.stem: p for p in desc_dir.glob("*.txt")}
|
||||||
|
sign_files = {p.stem: p for p in sign_dir.glob("*.txt")}
|
||||||
|
|
||||||
|
common_keys = desc_files.keys() & sign_files.keys()
|
||||||
|
if not common_keys:
|
||||||
|
print("⚠️ Нет общих файлов между каталогами описаний и сигналов.")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
breeds = {}
|
||||||
|
for key in sorted(common_keys):
|
||||||
|
desc_path = desc_files[key]
|
||||||
|
sign_path = sign_files[key]
|
||||||
|
try:
|
||||||
|
description = read_text_file(desc_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Не удалось прочитать описание {desc_path}: {e}")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
signs = read_json_file(sign_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Не удалось прочитать сигналы {sign_path}: {e}")
|
||||||
|
continue
|
||||||
|
breeds[key] = (description, signs)
|
||||||
|
return breeds
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 5️⃣ Генерация id (если нужно уникальное)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def generate_id(name: str, alias: str) -> str:
|
||||||
|
"""Генерирует MD5‑hash из `name:alias`."""
|
||||||
|
key = f"{name}:{alias}"
|
||||||
|
return hashlib.md5(key.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 6️⃣ Генерация SQL‑файла
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def write_sql_file(breeds: dict, out_path: pathlib.Path):
|
||||||
|
"""Создаёт один .sql‑файл, содержащий INSERT‑операции для всех пород."""
|
||||||
|
if not breeds:
|
||||||
|
print("⚠️ Нет данных для генерации SQL.")
|
||||||
|
return
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
header = (
|
||||||
|
"-- Автоматически сгенерированный скрипт вставки пород\n"
|
||||||
|
"-- Запустить: psql -f beerds_insert.sql\n\n"
|
||||||
|
"BEGIN;\n"
|
||||||
|
)
|
||||||
|
lines.append(header)
|
||||||
|
|
||||||
|
# Формируем один блок INSERT с несколькими строками VALUES
|
||||||
|
# 1. Столбцы
|
||||||
|
cols = "(id, name, descriptions, signs, alias)"
|
||||||
|
# 2. Генерируем строки VALUES
|
||||||
|
value_lines = []
|
||||||
|
for name, (description, signs) in breeds.items():
|
||||||
|
alias = name # можно изменить при необходимости
|
||||||
|
breed_id = generate_id(name, alias)
|
||||||
|
# Подготовка значений: экранирование апострофов и JSON
|
||||||
|
escaped_name = name.replace("_", " ").replace("'", "''")
|
||||||
|
escaped_alias = alias.replace("'", "''")
|
||||||
|
escaped_desc = description.replace("'", "''")
|
||||||
|
# JSON как строка (в PostgreSQL json можно передавать как текст)
|
||||||
|
json_str = json.dumps(signs, ensure_ascii=False).replace("'", "''")
|
||||||
|
value_line = f"('{breed_id}', '{escaped_name}', '{escaped_desc}', '{json_str}', '{escaped_alias}')"
|
||||||
|
value_lines.append(value_line)
|
||||||
|
|
||||||
|
# Объединяем VALUES через запятую
|
||||||
|
values_section = ",\n".join(value_lines)
|
||||||
|
insert_stmt = (
|
||||||
|
f"INSERT INTO beerds.beerds {cols}\n"
|
||||||
|
f"VALUES\n{values_section}\n"
|
||||||
|
f"ON CONFLICT (id) DO UPDATE\n"
|
||||||
|
f" SET name = EXCLUDED.name,\n"
|
||||||
|
f" descriptions = EXCLUDED.descriptions,\n"
|
||||||
|
f" signs = EXCLUDED.signs,\n"
|
||||||
|
f" alias = EXCLUDED.alias;\n"
|
||||||
|
)
|
||||||
|
lines.append(insert_stmt)
|
||||||
|
lines.append("\nCOMMIT;")
|
||||||
|
|
||||||
|
# Записываем в файл
|
||||||
|
out_path.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
print(f"✅ SQL‑файл успешно сгенерирован: {out_path}")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7️⃣ Основная точка входа
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
print("🔍 Читаем файлы…")
|
||||||
|
breeds_map = build_breed_map(DESC_DIR, SIGN_DIR)
|
||||||
|
|
||||||
|
if not breeds_map:
|
||||||
|
print("❌ Ничего не найдено. Завершение.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Путь для вывода SQL‑файла
|
||||||
|
sql_file_path = BASE_DIR / "beerds_insert.sql"
|
||||||
|
print(f"📝 Записываем INSERT‑операции в файл: {sql_file_path}")
|
||||||
|
write_sql_file(breeds_map, sql_file_path)
|
||||||
|
|
||||||
|
print("🎉 Готово!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_folder_structure(root_path):
|
||||||
|
result = {}
|
||||||
|
# Поддерживаемые форматы изображений
|
||||||
|
image_ext = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff"}
|
||||||
|
|
||||||
|
# Основные категории (cat и dog)
|
||||||
|
for category in ["cat", "dog"]:
|
||||||
|
category_path = Path(root_path) / category
|
||||||
|
if not category_path.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
category_dict = {}
|
||||||
|
# Обрабатываем подпапки внутри категории
|
||||||
|
for subfolder in category_path.iterdir():
|
||||||
|
if subfolder.is_dir():
|
||||||
|
# Собираем изображения
|
||||||
|
images = [
|
||||||
|
file.name for file in subfolder.iterdir() if file.is_file() and file.suffix.lower() in image_ext
|
||||||
|
]
|
||||||
|
category_dict[subfolder.name] = sorted(images)
|
||||||
|
|
||||||
|
result[category] = category_dict
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_json(data, output_file):
|
||||||
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Настройки
|
||||||
|
ASSETS_DIR = "server/static/assets"
|
||||||
|
OUTPUT_JSON = "structure.json"
|
||||||
|
|
||||||
|
# Генерация структуры
|
||||||
|
structure = generate_folder_structure(ASSETS_DIR)
|
||||||
|
|
||||||
|
# Сохранение в файл
|
||||||
|
save_to_json(structure, OUTPUT_JSON)
|
||||||
|
print(f"JSON структура сохранена в файл: {OUTPUT_JSON}")
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue