Files
999/scripts/extract_and_link_illustrations.py
T

260 lines
12 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
extract_and_link_illustrations.py
=================================
Извлекает все 75 гравюр/иллюстраций из словаря Х.Э. Керлота:
1. Сохраняет изображения в папку assets/illustrations/
2. Привязывает их к словарным статьям и подписям
3. Обогащает ТЕСТОВУЮ базу (assets/data/oracle_passports_staging.json) для слов из 1000
4. Привязывает к базе внешних символов (assets/data/oracle_external_symbols.json)
5. Создает единый реестр иллюстраций assets/data/oracle_illustrations_catalog.json
"""
import os
import sys
import json
import re
import subprocess
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
import fitz
DICT_PDF = PROJECT_ROOT / "dictionaries" / "Кирло Х. - Словарь символов. 1000 статей о важнейших понятиях религии, литературы, архитектуры, истории - 2010.pdf"
ILLUSTRATIONS_DIR = PROJECT_ROOT / "assets" / "illustrations"
CATALOG_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_illustrations_catalog.json"
STAGING_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_passports_staging.json"
EXTERNAL_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_external_symbols.json"
ENRICHER_EXTERNAL_PATH = PROJECT_ROOT / "oracle_enricher" / "data" / "oracle_external_symbols.json"
def clean_spaces(text: str) -> str:
return re.sub(r'\s+', ' ', text).strip()
def transliterate_to_slug(text: str) -> str:
"""Транслитерация в безопасное имя файла."""
ru_to_en = {
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm',
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
}
slug = []
for c in text.lower():
if c in ru_to_en:
slug.append(ru_to_en[c])
elif c.isalnum():
slug.append(c)
elif c in [' ', '-', '_']:
slug.append('_')
res = re.sub(r'_+', '_', ''.join(slug)).strip('_')
return res[:30] if res else 'symbol'
def get_page_text(p_num: int) -> str:
"""Извлекает текст страницы PDF через pdftotext (1-indexed)."""
try:
cmd = ["pdftotext", "-f", str(p_num), "-l", str(p_num), str(DICT_PDF), "-"]
out = subprocess.check_output(cmd)
return out.decode("utf-8", errors="ignore").encode("latin1", errors="ignore").decode("cp1251", errors="ignore")
except Exception:
return ""
def main():
print("=" * 75)
print("🎨 ИЗВЛЕЧЕНИЕ И ПРИВЯЗКА ИЛЛЮСТРАЦИЙ / ГРАВЮР СИМВОЛОВ")
print(f"📖 Источник: {DICT_PDF.name}")
print(f"📁 Папка сохранения: {ILLUSTRATIONS_DIR}")
print("=" * 75)
if not DICT_PDF.exists():
print(f"[ERROR] Файл словаря не найден: {DICT_PDF}")
return
ILLUSTRATIONS_DIR.mkdir(parents=True, exist_ok=True)
doc = fitz.open(str(DICT_PDF))
print(f"Всего страниц в PDF: {len(doc)}")
# 1. Загрузка баз данных
with open(STAGING_DB_PATH, "r", encoding="utf-8") as f:
staging_db = json.load(f)
with open(EXTERNAL_DB_PATH, "r", encoding="utf-8") as f:
external_db = json.load(f)
# Индекс канонических слов 1000 знаков
core_index = {}
for idx_str, entry in staging_db.items():
w = entry.get("word", "").lower().strip()
if w:
core_index[w] = idx_str
# 2. Карта заголовков по страницам
print("\nСканирование структуры статей по страницам...")
page_to_head = {}
current_head = "ВВЕДЕНИЕ"
page_texts = {}
for p in range(50, len(doc)):
p_num = p + 1
txt = get_page_text(p_num)
page_texts[p_num] = txt
lines = [l.strip() for l in txt.splitlines() if l.strip()]
for l in lines:
if l.isupper() and 2 <= len(l) <= 35 and re.match(r'^[А-ЯЁ\s\-]+$', l):
if l not in ['СЛОВАРЬ СИМВОЛОВ', 'СОДЕРЖАНИЕ', 'АЛФАВИТНЫЙ УКАЗАТЕЛЬ']:
current_head = l
page_to_head[p_num] = current_head
# Специфические уточнения для страниц, где иллюстрация относится к предыдущей статье
page_overrides = {
57: ("АЛХИМИЯ", "Атенор, печь алхимиков (реконструкция по старой гравюре)"),
87: ("ВЕСЫ", "Зодиакальный знак Весы"),
111: ("ГЕРАЛЬДИЧЕСКИЕ СИМВОЛЫ", "Геральдический орел"),
113: ("ГЕРОЙ", "Арданари Ишвара (андрогинный аспект Шивы)"),
128: ("ГРАФИКА", "Единство — происхождение и центр бесконечности"),
129: ("ГРАФИКА", "Центр бесконечности, излучение или первопричина"),
140: ("ДЕВА", "Знак зодиака Дева"),
198: ("КАДУЦЕЙ", "Древнее шумерское изображение кадуцея"),
234: ("КРУГ", "Китайский знак ян-инь, окруженный восемью триграммами"),
241: ("ЛАБИРИНТ", "Лабиринт — часы, олицетворяющие единство времени и космоса (старинная гравюра)"),
402: ("СОЛНЦЕ", "Солнце и луна, активный и пассивный принципы (гравюра из «De Intellectu», 1510)"),
479: ("ЧЕРЕПАХА", "Восточное космологическое изображение черепахи"),
502: ("ЯКОРЬ", "Якорь спасения, ранний христианский символ"),
}
# 3. Извлечение иллюстраций
catalog = []
extracted_count = 0
staging_linked_count = 0
external_linked_count = 0
print("\nИзвлечение графических иллюстраций...")
for p in range(50, len(doc)):
p_num = p + 1
imgs = doc[p].get_images()
if not imgs:
continue
txt = page_texts.get(p_num, "")
lines = [l.strip() for l in txt.splitlines() if l.strip()]
for idx, img_info in enumerate(imgs):
xref = img_info[0]
base_img = doc.extract_image(xref)
w, h = base_img["width"], base_img["height"]
ext = base_img["ext"]
# Фильтр мелких разделителей и пустых артефактов
if w < 120 or h < 120:
continue
# Определяем заголовок и подпись
if p_num in page_overrides:
head_symbol, caption = page_overrides[p_num]
else:
head_symbol = page_to_head.get(p_num, "СИМВОЛ")
# Ищем подпись: короткая изолированная строка
caption = ""
for l in lines:
if not l.isupper() and not l.endswith("-") and 8 <= len(l) <= 90 and not l.isdigit():
if any(w in l.lower() for w in ["знак", "символ", "гравюр", "изображен", "схема", "рисунок", "бог", "образ"]):
caption = l
break
if not caption:
caption = f"Иллюстрация к понятию «{head_symbol.title()}»"
# Генерируем красивое имя файла
slug = transliterate_to_slug(head_symbol)
fname = f"cirlot_{p_num:03d}_{slug}_{idx}.{ext}"
file_path = ILLUSTRATIONS_DIR / fname
rel_asset_path = f"assets/illustrations/{fname}"
with open(file_path, "wb") as f_out:
f_out.write(base_img["image"])
extracted_count += 1
# Создаем объект метаданных иллюстрации
ill_item = {
"id": f"cirlot_{p_num}_{idx}",
"file": rel_asset_path,
"symbol": head_symbol,
"normalized": head_symbol.lower(),
"caption": caption,
"source": "Х.Э. Керлот · «Словарь символов» (2010)",
"page": p_num,
"width": w,
"height": h,
}
norm = head_symbol.lower()
# Проверяем привязку к 1000 знаков (Staging)
matched_core_id = None
if norm in core_index:
matched_core_id = core_index[norm]
elif norm.endswith("ы") and norm[:-1] in core_index:
matched_core_id = core_index[norm[:-1]]
elif norm.endswith("и") and norm[:-1] in core_index:
matched_core_id = core_index[norm[:-1]]
if matched_core_id:
ill_item["is_core_1000"] = True
ill_item["core_id"] = matched_core_id
target_entry = staging_db[matched_core_id]
target_ills = target_entry.setdefault("illustrations", [])
if not any(x.get("file") == rel_asset_path for x in target_ills):
target_ills.append(ill_item)
staging_linked_count += 1
else:
ill_item["is_core_1000"] = False
ill_item["core_id"] = None
# Привязываем к базе внешних символов
if norm in external_db:
ext_ills = external_db[norm].setdefault("illustrations", [])
if not any(x.get("file") == rel_asset_path for x in ext_ills):
ext_ills.append(ill_item)
external_linked_count += 1
catalog.append(ill_item)
# 4. Сохранение каталога и обновленных баз
print(f"\n💾 Сохранение общего каталога: {CATALOG_PATH}...")
CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(CATALOG_PATH, "w", encoding="utf-8") as f:
json.dump(catalog, f, ensure_ascii=False, indent=2)
print(f"💾 Обновление тестовой базы (staging): {STAGING_DB_PATH}...")
with open(STAGING_DB_PATH, "w", encoding="utf-8") as f:
json.dump(staging_db, f, ensure_ascii=False, indent=2)
print(f"💾 Обновление базы внешних символов: {EXTERNAL_DB_PATH}...")
with open(EXTERNAL_DB_PATH, "w", encoding="utf-8") as f:
json.dump(external_db, f, ensure_ascii=False, indent=2)
with open(ENRICHER_EXTERNAL_PATH, "w", encoding="utf-8") as f:
json.dump(external_db, f, ensure_ascii=False, indent=2)
print("\n" + "=" * 75)
print("📊 ИТОГОВЫЙ ОТЧЕТ ПО ИЛЛЮСТРАЦИЯМ:")
print(f"• Всего извлечено гравюр/изображений: {extracted_count}")
print(f"• Привязано к знакам канона 1000 в staging: {staging_linked_count}")
print(f"• Привязано к внешним символам: {external_linked_count}")
print(f"• Папка иллюстраций: {ILLUSTRATIONS_DIR} ({len(list(ILLUSTRATIONS_DIR.iterdir()))} файлов)")
print(f"• Каталог иллюстраций: {CATALOG_PATH}")
print("=" * 75)
if __name__ == "__main__":
main()