# Выжимка из контекста: Домашняя библиотека ## Что построено **Локальный OPDS-каталог + веб-интерфейс** для копии библиотеки lib.rus.ec (~579 000 книг) на TV-приставке с Armbian. ## Железо и окружение - **Устройство**: TV-приставка Vontar, Armbian, ARM - **Диски**: `/mnt/hdd` — основной - **Docker**: контейнеры Immich (фото), Home Assistant, qBittorrent, Adminer - **PostgreSQL**: используется общий с Immich (`immich_postgres`), база `books` - Пароль: `Karabas@Barabas`, пользователь: `postgres` - **Фиксированный IP**: `172.20.0.4` (сеть `immich_immich_net`, подсеть `172.20.0.0/16`) - **OPDS-сервер**: контейнер `opds_server` на порту **8085** (порт 8080 занят qBittorrent) - **Адрес библиотеки**: `http://192.168.30.2:8085/` ## Расположение файлов ``` /mnt/hdd/opds/ # проект OPDS ├── opds_server.py # главный Flask-сервер ├── db_utils.py # подключение к БД ├── author_utils.py # работа с авторами ├── book_utils.py # извлечение fb2, обложки, конвертация ├── parse_inpx.py # импорт книг ├── parse_genres.py # загрузка жанров ├── genres.xml # справочник жанров fb2 ├── Dockerfile └── docker-compose.yml /mnt/hdd/share/Torrents/Downloaded/Libruks/ ├── Библиотека Либрусек для архивов 17-08-2026.inpx ├── Библиотека Либрусек для архивов 18-09-2026.inpx ├── Libruks1086.zip └── Архивы Либрусек/ # 3726 zip-архивов с fb2 ├── fb2-000065-005477.zip ├── fb2-069327-070122.zip └── ... (по диапазонам ID) /root/docker-compose.yml # Immich (с фиксированным IP postgres) ``` ## Структура базы данных | Таблица | Назначение | |---------|-----------| | `authors` | Авторы (нормализованные: full_name, last_name, first_name, middle_name, name_hash, first_letter) | | `series` | Серии (name, name_hash, first_letter) | | `genres` | Жанры (code, title_ru, title_en, parent_code, root_code, is_root, is_custom, genre_alt[]) | | `books` | Книги (title, internal_id, internal_id2, file_size, format, added_date, language, source, series_id, series_number, fts) | | `book_authors` | Связь книг↔авторов (book_id, author_id, position) | | `book_genres` | Связь книг↔жанров (book_id, genre_id) | **Особенности:** - Полнотекстовый поиск через `fts tsvector` (русский язык) - Хеши (MD5) для быстрого поиска - Поле `source` для поддержки нескольких источников - Нормализация: авторы/серии/жанры вынесены в отдельные таблицы ## Формат inpx (lib.rus.ec) Текстовый файл с разделителем `\x04`: ``` AUTHOR \x04 GENRE \x04 TITLE \x04 SERIES \x04 SERNO \x04 FILE \x04 SIZE \x04 LIBID \x04 DEL \x04 EXT \x04 DATE \x04 LANG \x04 KEYWORDS ``` - Авторы: `Фамилия,Имя,Отчество` через `:` для нескольких - Жанры: коды fb2 через `:` (например, `sf_history:network_literature`) - Особенность архивов: **fb2 внутри zip-архивов может быть вложенным zip** — нужно распаковывать дважды ## Что реализовано в веб-интерфейсе - ✅ Главная с поиском и навигацией - ✅ Поиск (полнотекстовый + по автору) - ✅ Авторы с группировкой по алфавиту (умная навигация: Па → Пел → Пелевин) - ✅ Страница автора (серии, книги, сборники с пометкой соавторов) - ✅ Страница книги (обложка, аннотация, оглавление) - ✅ Чтение глав (конвертация fb2→HTML на лету, встроенные картинки, навигация ← пред/оглавление↑/след →) - ✅ Скачивание fb2 (с обработкой вложенных zip) - ✅ OPDS 1.2 для читалок (FBReader) ## Что НЕ реализовано / в планах - ❌ Страница серий (список + страница серии с книгами) - ❌ Страница жанров (дерево + фильтрация) - ❌ Обложки с ленивой загрузкой (обсуждали: заглушка → загрузка в фоне → кэш в `/mnt/hdd/opds/covers/`) - ❌ OPDS 2.0 (обсуждали как второй эндпоинт `/opds2`) - ❌ Редактирование жанров (кастомные жанры с `is_custom=TRUE` ждут ручного заполнения) - ❌ Языковой фильтр (56 языков в базе, но основные — ru) ## Новый импорт: сканирование файловой системы **Проблема INPX:** файл содержит ссылки на книги, которых нет на диске (удалены, не скачаны). **Решение:** скрипт-сканер каталога — альтернатива INPX. ### Логика работы 1. Принимает каталог как параметр 2. Обходит все файлы в каталоге и подкаталогах 3. Определяет новые/изменённые файлы через таблицу `file_scan_log` 4. Новые/изменённые файлы → распаковка → парсинг FB2 → добавление в БД 5. Исчезнувшие файлы → удаление связанных книг из БД ### Поддержка форматов - `.zip` → внутри может быть `.zip` → внутри может быть `.fb2` - Вложенность: до 3 уровней - RAR: бонусная поддержка ### Таблица `file_scan_log` | Поле | Тип | Назначение | |------|-----|-----------| | file_path | TEXT | Полный путь к файлу | | file_size | INTEGER | Размер файла | | file_hash | TEXT | MD5/SHA256 хеш содержимого | | updated_at | TIMESTAMP | Последнее обновление | ### Удаление книг - Файл исчез с диска → книга удаляется из БД безвозвратно - Файл изменён → старые книги удаляются, парсится заново ### Источник - Отдельная строка в `books.source` (например, `"file_scan"`) ## Ключевые решения - **Язык в скобках**: для не-русских книг показывать `(en)`, `(uk)` и т.д. - **Сборники**: если авторов >1 — пометка `👥 соавторы: X, Y` (до 3) или `👥 N авторов` (больше) - **Порядок книг автора**: от новых к старым (`added_date DESC NULLS LAST`) - **Серии на странице автора**: все, где автор участвует, по алфавиту - **Название**: "Домашняя библиотека" (не "Моя библиотека") - **Модульная структура**: разбить на модули (db, author, book, series, genre utils) - **Git**: хранить всю информацию в репозитории ## Текущие проблемы / нюансы 1. **IP PostgreSQL менялся** после перезагрузки → исправлено фиксированным IP `172.20.0.4` 2. **Сеть Immich** конфликтовала с adminer → adminer отключён, сеть пересоздана как `immich_immich_net` 3. **Вложенные zip в архивах** → `get_book_fb2` распаковывает дважды 4. **VACUUM** нельзя в транзакции → `autocommit=True` перед VACUUM 5. **Дубликаты авторов** (`Лукьяненко,Сергей,` и `Лукьяненко,Сергей`) → исправлено нормализацией при импорте 6. **string_agg(DISTINCT ... ORDER BY ...)** — ошибка в PostgreSQL → переписано на подзапросы ## Полезные команды ```bash # Обновление библиотеки новым inpx docker compose exec opds python parse_inpx.py \ "/path/to/новый.inpx" \ "dbname=books user=postgres password=Karabas@Barabas host=172.20.0.4" \ "lib.rus.ec" # Проверка количества книг docker exec -it immich_postgres psql -U postgres -d books -c "SELECT COUNT(*) FROM books;" # Кастомные жанры docker exec -it immich_postgres psql -U postgres -d books -c " SELECT g.code, COUNT(bg.book_id) as cnt FROM genres g JOIN book_genres bg ON bg.genre_id = g.id WHERE g.is_custom = TRUE GROUP BY g.code ORDER BY cnt DESC;" # Пересборка OPDS cd /mnt/hdd/opds && docker compose up -d --build ``` ## Следующий шаг Создание Git-репозитория `opds-library` со структурой: - `sql/` — схема БД - `scripts/` — парсеры - `app/` — Flask-сервер (модули) - `data/` — genres.xml - `docker-compose.yml`, `Dockerfile`, `README.md` С этого места продолжаем.