127 lines
6.5 KiB
Markdown
127 lines
6.5 KiB
Markdown
# 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 тега `<i>` в EPUB**. Плюс calibre
|
|
размечает основной текст как `<h2>` (2817 штук), потому что определяет заголовки
|
|
по кеглю относительно самого частого.
|
|
|
|
`scripts/pdf2html.py` извлекает текст через PyMuPDF, где начертание берётся из
|
|
свойств span'а, и сам раскладывает блоки по семантике.
|
|
|
|
## Что делает скрипт
|
|
|
|
- курсив/полужирный — по имени шрифта span'а (`-It`, `Italic`, `Bold`, `Semibold`);
|
|
- заголовки — по кеглю: `>= 28` или декоративный шрифт — `h1.title`,
|
|
`>= 24` — `h1` (главы и части), `>= 17` — `h2` (подзаголовок с датой);
|
|
- врезки (дневник, чаты, слайды) — отдельный шрифт семейства → `p.note`;
|
|
- абзац = блок PDF; блок без красной строки в начале страницы приклеивается
|
|
к абзацу с предыдущей страницы;
|
|
- переносы на конце строки снимаются, соседние `</i><i>` схлопываются;
|
|
- иллюстрации выгружаются в `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` появится в списке доступных при следующем запуске сессии.
|
|
Обновление на любом ПК: `git -C ~/.claude/skills/pdf-to-kindle pull`.
|
|
|
|
Автообновление подцеплено к таймеру синхронизации памяти — вторая строка
|
|
`ExecStart` в `~/.config/systemd/user/claude-memory-sync.service` (префикс `-`
|
|
означает «не считать ошибкой, если скил не склонирован»):
|
|
|
|
```ini
|
|
ExecStart=-/usr/bin/git -C %h/.claude/skills/pdf-to-kindle pull --rebase --autostash --quiet
|
|
```
|
|
|
|
На новом ПК строку нужно добавить вручную и выполнить
|
|
`systemctl --user daemon-reload`.
|
|
|
|
## Требования
|
|
|
|
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/` на устройстве.
|
|
|
|
## Проверка результата
|
|
|
|
```bash
|
|
python3 - <<'EOF'
|
|
import re
|
|
t = open('out/book.html').read()
|
|
print('курсив:', t.count('<i>'), 'полужирный:', t.count('<b>'))
|
|
print('заголовки:', len(re.findall(r'<h1', t)))
|
|
print('двойные пробелы:', re.sub('<[^>]+>', '', t).count(' '))
|
|
EOF
|
|
```
|
|
|
|
Двойных пробелов должно быть единицы, курсива — сотни. Ноль курсива означает,
|
|
что имена шрифтов в PDF не попали под правило в `style()`.
|
|
|
|
## Ограничения
|
|
|
|
- рассчитан на PDF с текстовым слоем; сканы требуют OCR и сюда не годятся;
|
|
- пороги кеглей и семейство шрифта для врезок — константы под конкретную вёрстку,
|
|
правятся руками по выводу `--fonts`;
|
|
- колонтитулы и номера страниц не вырезаются: в PDF от calibre их нет.
|
|
Для типографского PDF понадобится отбрасывать блоки по координате `y`.
|
|
|
|
## Проверено на
|
|
|
|
`The Unicorn Project` (Gene Kim), 399 страниц: 40 заголовков, оглавление на
|
|
62 пункта, 430 курсивных фрагментов, 178 врезок, 5 иллюстраций, ~733 тыс. знаков.
|