Files
opds-library/OPDS_LIBRARY_CONTEXT.md

10 KiB
Raw Permalink Blame History

Выжимка из контекста: Домашняя библиотека

Что построено

Локальный 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.

Логика работы

  1. Принимает каталог как параметр
  2. Обходит все файлы в каталоге и подкаталогах
  3. Определяет новые/изменённые файлы через таблицу file_scan_log
  4. Новые/изменённые файлы → распаковка → парсинг FB2 → добавление в БД
  5. Исчезнувшие файлы → удаление связанных книг из БД

Поддержка форматов

  • .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: хранить всю информацию в репозитории

Текущие проблемы / нюансы

  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 → переписано на подзапросы

Полезные команды

# Обновление библиотеки новым 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

С этого места продолжаем.