# pdf2epub
Конвертация PDF, собранного из электронной книги (Producer: calibre), в EPUB/AZW3
для Kindle **с сохранением форматирования**: курсив, полужирный, заголовки глав,
врезки, иллюстрации, склейка абзацев через границы страниц.
## Зачем не `ebook-convert` напрямую
Прямая конвертация `ebook-convert book.pdf book.epub` теряет курсив: poppler
определяет начертание по имени шрифта и не опознаёт сокращённые имена вроде
`MinionPro-It` (без подстроки `Italic`). На «The Unicorn Project» получилось
**435 курсивных фрагментов в PDF против 1 тега `` в EPUB**. Плюс calibre
размечает основной текст как `` (2817 штук), потому что определяет заголовки
по кеглю относительно самого частого.
`scripts/pdf2html.py` извлекает текст через PyMuPDF, где начертание берётся из
свойств span'а, и сам раскладывает блоки по семантике.
## Что делает скрипт
- курсив/полужирный — по имени шрифта span'а (`-It`, `Italic`, `Bold`, `Semibold`);
- заголовки — по кеглю: `>= 28` или декоративный шрифт — `h1.title`,
`>= 24` — `h1` (главы и части), `>= 17` — `h2` (подзаголовок с датой);
- врезки (дневник, чаты, слайды) — отдельный шрифт семейства → `p.note`;
- абзац = блок PDF; блок без красной строки в начале страницы приклеивается
к абзацу с предыдущей страницы;
- переносы на конце строки снимаются, соседние `
` схлопываются;
- иллюстрации выгружаются в `images/`, обложка рендерится со страницы 1 в 150 dpi;
- позиционирование не сохраняется намеренно — текст должен течь под любой
размер шрифта на читалке.
## Подключение как скил Claude Code
Репозиторий одновременно является скилом (`SKILL.md` в корне) и клонируется
**прямо в каталог скила** — никаких симлинков и зависимости от `~/Projects`,
структура каталогов на разных ПК может не совпадать:
```bash
git clone https://git.katze-lab.ru/chesirecatt/pdf2epub.git ~/.claude/skills/pdf-to-kindle
```
Скил `pdf-to-kindle` появится в списке доступных при следующем запуске сессии.
Ручной клон нужен только если нет синхронизации памяти. **Штатный путь —
автоматический:** репозиторий перечислен в массиве `SKILL_REPOS` в `sync.sh`
репозитория [`claude-memory`](https://git.katze-lab.ru/chesirecatt/claude-memory),
который крутится по таймеру `claude-memory-sync.timer` раз в 10 минут. На новом
ПК скил клонируется сам при первом же прогоне синхронизации памяти, дальше
обновляется. Ручных шагов и правки systemd-юнита не требуется.
Синхронизация односторонняя (`pull`): правки вносятся и пушатся руками из
`~/.claude/skills/pdf-to-kindle`.
Чтобы добавить в автосинхронизацию ещё один скил — строка в `SKILL_REPOS`
формата `" <имя каталога скила>"`.
## Требования
PyMuPDF и calibre:
```bash
python3 -m venv ~/.venvs/pdf2epub
~/.venvs/pdf2epub/bin/pip install pymupdf
sudo dnf install calibre # если ebook-convert ещё нет
```
## Использование
Разведка — какие в PDF шрифты и кегли (пороги в скрипте подобраны под вёрстку
15pt/letter, для другой книги их правят по этому выводу):
```bash
~/.venvs/pdf2epub/bin/python scripts/pdf2html.py --fonts book.pdf
```
Конвертация:
```bash
~/.venvs/pdf2epub/bin/python scripts/pdf2html.py book.pdf out/book.html
ebook-convert out/book.html "Название.epub" \
--title="Название" \
--authors="Автор" \
--language=en \
--cover=out/images/cover.jpg \
--level1-toc='//h:h1' --level2-toc='//h:h2' \
--page-breaks-before='//h:h1' \
--no-default-epub-cover
ebook-convert "Название.epub" "Название.azw3"
```
`--level1-toc`/`--level2-toc` обязательны: без них calibre строит оглавление
своей эвристикой и ломает разбивку по главам.
## Куда класть на Kindle
- `.epub` — через Send to Kindle (веб или почта), Amazon конвертирует на своей стороне;
- `.azw3` — по USB прямо в `documents/` на устройстве.
## Перевод (опционально)
Перевод делает сторонний проект
[`vetermanve/book_translator`](https://github.com/vetermanve/book_translator)
(DeepSeek или локальная Ollama). **Репозиторий не модифицируется** — он
вызывается через свой штатный CLI и форматы файлов. `scripts/translate.py` —
мост: раскладывает наш XHTML в его входной JSON, запускает
`03_translate_parallel.py --all`, собирает результат обратно.
Подготовка (один раз):
```bash
git clone https://github.com/vetermanve/book_translator.git ~/Projects/book_translator
~/.venvs/pdf2epub/bin/pip install openai python-dotenv # openai нет в их requirements.txt
export DEEPSEEK_API_KEY=... # либо .env в рабочем каталоге, либо USE_OLLAMA
```
Сначала проверка, нужен ли перевод вообще (язык, объём; ничего не тратит):
```bash
~/.venvs/pdf2epub/bin/python scripts/translate.py out/book.html out/book.ru.html \
--repo ~/Projects/book_translator --workdir out/tr --check-only
```
Если книга уже на целевом языке, скрипт откажется переводить (`--force`
переопределяет). Сам перевод:
```bash
~/.venvs/pdf2epub/bin/python scripts/translate.py out/book.html out/book.ru.html \
--repo ~/Projects/book_translator --workdir out/tr --workers 12
```
Прерванный перевод возобновляется: сторонний скрипт помнит готовые главы.
Дальше `out/book.ru.html` упаковывается тем же `ebook-convert` с
`--language=ru`.
Курсив и полужирный переживают внешний переводчик в виде маркеров
`⟦i⟧…⟦/i⟧`; на обратном пути баланс маркеров проверяется по каждому абзацу.
Картинки, заголовки и порядок блоков вообще не покидают наш скрипт — наружу
уходит только текст, сборка идёт по индексам. В отчёте после перевода:
«без перевода» — главы, вернувшиеся с другим числом абзацев (оставлен
оригинал), «разметка потеряна» — абзацы, где модель испортила маркеры и
курсив снят. Оба числа должны быть близки к нулю.
Самопроверка моста без сети и ключей:
```bash
cd scripts && python3 test_translate.py
```
## Проверка результата
```bash
python3 - <<'EOF'
import re
t = open('out/book.html').read()
print('курсив:', t.count(''), 'полужирный:', t.count(''))
print('заголовки:', len(re.findall(r']+>', '', t).count(' '))
EOF
```
Двойных пробелов должно быть единицы, курсива — сотни. Ноль курсива означает,
что имена шрифтов в PDF не попали под правило в `style()`.
## Ограничения
- рассчитан на PDF с текстовым слоем; сканы требуют OCR и сюда не годятся;
- пороги кеглей и семейство шрифта для врезок — константы под конкретную вёрстку,
правятся руками по выводу `--fonts`;
- колонтитулы и номера страниц не вырезаются: в PDF от calibre их нет.
Для типографского PDF понадобится отбрасывать блоки по координате `y`.
## Проверено на
`The Unicorn Project` (Gene Kim), 399 страниц: 40 заголовков, оглавление на
62 пункта, 430 курсивных фрагментов, 178 врезок, 5 иллюстраций, ~733 тыс. знаков.