10 KiB
10 KiB
Выжимка из контекста: Домашняя библиотека
Что построено
Локальный 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) |
Особенности:
- Полнотекстовый поиск через
tsvector+ GIN-индексы (русский язык) - Хеши (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.
Логика работы
- Принимает каталог как параметр
- Обходит все файлы в каталоге и подкаталогах
- Определяет новые/изменённые файлы через таблицу
file_scan_log - Новые/изменённые файлы → распаковка → парсинг FB2 → добавление в БД
- Исчезнувшие файлы → удаление связанных книг из БД
Поддержка форматов
.zip→ внутри может быть.zip→ внутри может быть.fb2- Вложенность: до 3 уровней
- RAR: бонусная поддержка
Структура хранения (финальное решение)
- file_scan_log хранит архивы:
archive_path,archive_hash(хэш всего ZIP),file_size,updated_at - books хранит:
archive_path+fb2_name(путь к FB2 внутри архива) - При удалении архива → удаляем все книги из этого архива
- При изменении архива → удаляем старые книги, парсим заново
- Дубликаты с одинаковым содержимым не вызывают лишней работы (проверка по хэшу архива)
Удаление книг
- Файл (архив) исчез с диска → книги удаляются из БД безвозвратно
- Файл изменён → старые книги удаляются, парсится заново
Источник
- Отдельная строка в
books.source(например,"file_scan")
Ключевые решения
- Язык в скобках: для не-русских книг показывать
(en),(uk)и т.д. - Сборники: если авторов >1 — пометка
👥 соавторы: X, Y(до 3) или👥 N авторов(больше) - Порядок книг автора: от новых к старым (
added_date DESC NULLS LAST) - Серии на странице автора: все, где автор участвует, по алфавиту
- Название: "Домашняя библиотека" (не "Моя библиотека")
- Модульная структура: разбить на модули (db, author, book, series, genre utils)
- Git: хранить всю информацию в репозитории
Текущие проблемы / нюансы
- IP PostgreSQL менялся после перезагрузки → исправлено фиксированным IP
172.20.0.4 - Сеть Immich конфликтовала с adminer → adminer отключён, сеть пересоздана как
immich_immich_net - Вложенные zip в архивах →
get_book_fb2распаковывает дважды - VACUUM нельзя в транзакции →
autocommit=Trueперед VACUUM - Дубликаты авторов (
Лукьяненко,Сергей,иЛукьяненко,Сергей) → исправлено нормализацией при импорте - string_agg(DISTINCT ... ORDER BY ...) — ошибка в PostgreSQL → переписано на подзапросы
Полезные команды
# Обновление библиотеки новым 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.xmldocker-compose.yml,Dockerfile,README.md
С этого места продолжаем.