Files
opds-library/OPDS_LIBRARY_CONTEXT.md

175 lines
10 KiB
Markdown
Raw Permalink 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.
# Выжимка из контекста: Домашняя библиотека
## Что построено
**Локальный 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 → переписано на подзапросы
## Полезные команды
```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`
С этого места продолжаем.