Skip to content

Перевод английской PDF-книги в русский EPUB

В этом посте я расскажу, как перевести английскую книгу из формата PDF в русский EPUB с помощью локальной LLM (Ollama), marker-pdf и pandoc. Весь процесс выполняется на одной машине.

В качестве примера используется книга Пола Хоффмана «The Man Who Loved Only Numbers» — биография Пола Эрдёша.

Замечание. Процесс ресурсоёмкий: перевод книги среднего размера локальной моделью занимает примерно 18–24 часа. Желательно иметь GPU и достаточно RAM для модели Ollama.


0. Предварительные требования

  • Ubuntu / Debian (или совместимый Linux).
  • Установленный и запущенный Ollama с подходящей моделью перевода (например, gemma4:26b или аналогичной).
  • Доступ в интернет.

Настройка прокси (если требуется)

Если требуется пропишите прокси в /etc/environment:

sudo nano /etc/environment
http_proxy="http://proxy.domain.com:3128"
https_proxy="http://proxy.domain.com:3128"
no_proxy="localhost,127.0.0.1,::1"

HTTP_PROXY="http://proxy.domain.com:3128"
HTTPS_PROXY="http://proxy.domain.com:3128"
NO_PROXY="localhost,127.0.0.1,::1"
source /etc/environment

Для Docker (если Ollama или вспомогательные сервисы запускаются в контейнерах):

sudo mkdir -p /etc/systemd/system/docker.service.d/
sudo nano /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.domain.com:3128"
Environment="HTTPS_PROXY=http://proxy.domain.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1"
sudo systemctl daemon-reload
sudo systemctl restart docker

1. Установка пакетов и подготовка окружения

sudo apt update && sudo apt install -y python3-pip python3-venv screen default-jre

Создаём рабочую директорию и виртуальное окружение Python:

sudo mkdir -p /opt/book-translate && sudo chown $USER:$USER /opt/book-translate
cd /opt/book-translate
python3 -m venv venv
source venv/bin/activate

Устанавливаем зависимости:

pip install marker-pdf
pip install requests pyyaml

2. Конвертация PDF → Markdown с помощью marker-pdf

Кладём исходный PDF в рабочую директорию:

/opt/book-translate/Paul_Hoffman_The_Man_Who_Loved_Only_Numbers.pdf

Запускаем конвертацию:

cd /opt/book-translate
source venv/bin/activate

marker_single Paul_Hoffman_The_Man_Who_Loved_Only_Numbers.pdf --output_dir ./output --page_range "0,11-302" --output_format markdown

Про номера страниц. marker-pdf использует 0-индексацию: страница 0 — это первая страница PDF (обложка), 11–302 — это страницы PDF с 12-й по 303-ю (мы пропускаем оглавление, благодарности и т. п. в начале книги).

После конвертации Markdown и извлечённые изображения окажутся в ./output/Paul_Hoffman_The_Man_Who_Loved_Only_Numbers/. Переходим в неё:

cd /opt/book-translate/output/Paul_Hoffman_The_Man_Who_Loved_Only_Numbers

3. Этап 1. Очистка Markdown

marker-pdf хорошо распознаёт текст, но оставляет мусор: обрывки колонтитулов, неправильные переносы, артефакты OCR. Имеет смысл написать пару Python-скриптов для автоматического исправления типовых проблем — это сильно дешевле, чем чинить руками или платить за это токенами LLM.

  • cleanup.py — первичная чистка.
  • cleanup2.py — вторая итерация после визуального просмотра.

Запускаем по очереди и просматриваем результат глазами.


4. Этап 2. Сборка глоссария

Глоссарий нужен для двух вещей:

  1. Согласованность. Каждое имя собственное и термин переводится одинаково во всех частях книги.
  2. Корректность. Мы сами решаем, как пишутся имена и термины по-русски, а не отдаём это «на откуп» модели.

Скрипт build_glossary.py автоматически вытаскивает кандидатов (повторяющиеся имена, технические термины) из текста. Очевидные вещи (названия стран и т. п.) отфильтровываются — модель с ними справится сама.

python3 build_glossary.py

build_glossary.py

На выходе получается glossary.yaml, который вы вычитываете и правите вручную.

glossary.yaml


5. Этап 3. Перевод

Скрипт перевода translate.py спроектирован с учётом того, что процесс длинный и должен переживать сбои. Ключевые свойства:

  • Возобновляемость. Прогресс сохраняется после каждого переведённого фрагмента — если процесс упадёт или вы оборвёте SSH, достаточно перезапустить скрипт, и он продолжит с места остановки.
  • Разбиение по абзацам. Текст режется на чанки по границам абзацев — модель никогда не получает оборванный посередине абзац.
  • Передача контекста. В каждый запрос подставляются последние переведённые абзацы, чтобы сохранялась связность повествования (местоимения, время, стиль).
  • Подстановка глоссария. В каждый промпт подкладывается glossary.yaml, чтобы имена и термины переводились единообразно.

translate.py

Запуск перевода

Поскольку процесс идёт ~18–24 часа, запускаем его внутри screen, чтобы он пережил отключение SSH-сессии:

screen -S translate
cd /opt/book-translate/output/Paul_Hoffman_The_Man_Who_Loved_Only_Numbers
source /opt/book-translate/venv/bin/activate
python3 translate.py

Полезные команды для работы со screen:

  • Отсоединиться от сессии: Ctrl+A, затем D.
  • Снова подключиться: screen -r translate.

Если скрипт упадёт — просто запустите его снова, прогресс сохранён.

Мониторинг прогресса

Быстрая проверка количества готовых чанков:

grep -c '"status"' translation_state.jsonl

Подробный отчёт со временем выполнения и количеством абзацев в каждом чанке:

python3 -c "
import json
with open('translation_state.jsonl', 'r') as f:
    for line in f:
        entry = json.loads(line)
        src_paras = len([p for p in entry['source_text'].split('\n\n') if p.strip()])
        status = entry['status']
        issues = '⚠' if 'issues' in status else '✓'
        elapsed = entry.get('elapsed_seconds', 0)
        print(f\"{entry['chunk_id']}: {status} | src={src_paras} paras | {elapsed:.0f}s {issues}\")
"

Постобработка

После того как перевод закончится, вы вычитываете и правите итоговый файл book_translated_ru.md. Дальше — несколько проверок:

1. Поиск «мусора» от модели (например, неудалённых служебных меток вида [Pабз1] или [intро]):

grep -nE '\[P[а-яА-Я0-9]{1,8}\]|\[int[А-Яа-я0-9]+\]' book_translated_ru.md

2. Проверка заголовков глав — все ли уровни на месте, нет ли «съеденных» решёток:

grep -nE '^#{1,3} ' book_translated_ru.md

3. Проверка ссылок на изображения — что все картинки, на которые ссылается Markdown, реально существуют:

grep -oE '!\[[^]]*\]\([^)]+\)' book_translated_ru.md | sed -E 's/.*\(([^)]+)\).*/\1/' | sort -u | while read -r img; do [ -f "$img" ] || echo "MISSING $img"; done; echo "---"; echo "Total unique refs: $(grep -oE '!\[[^]]*\]\([^)]+\)' book_translated_ru.md | sed -E 's/.*\(([^)]+)\).*/\1/' | sort -u | wc -l)"

6. Этап 4. Markdown → EPUB с помощью pandoc

Ставим свежий pandoc (версия из репозиториев Ubuntu обычно сильно отстаёт). Берём .deb со страницы релизов: https://github.com/jgm/pandoc/releases.

wget https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-1-amd64.deb
sudo dpkg -i pandoc-3.9.0.2-1-amd64.deb

pandoc-3.9.0.2-1-amd64.deb

Обложка книги

В качестве обложки удобно использовать первую страницу PDF — marker-pdf уже сохранил её как изображение:

cp _page_0_Figure_1.jpeg cover.jpeg

Метаданные EPUB

Создаём файл metadata.yaml:

cat > metadata.yaml <<'EOF'
---
title: "Человек, любивший только числа"
subtitle: "История Пола Эрдёша и поиск математической истины"
author: "Пол Хоффман"
language: ru-RU
publisher: "Личное издание"
date: 2026
rights: "© 1998 Paul Hoffman (original). Перевод для личного использования."
description: |
  Биография венгерского математика Пола Эрдёша — самого
  плодовитого математика XX века.
...
EOF

Сборка EPUB

pandoc book_translated_ru.md  --from=markdown+smart+tex_math_dollars  --to=epub3  --metadata-file=metadata.yaml  --epub-cover-image=cover.jpeg  --toc  --toc-depth=2  --split-level=1  --mathml  --output=book_ru.epub

Что означают ключевые опции:

  • --from=markdown+smart+tex_math_dollars — расширения Markdown: «умные» кавычки/тире и поддержка формул в виде $...$ и $$...$$.
  • --to=epub3 — выходной формат EPUB 3.
  • --metadata-file=metadata.yaml — подставить метаданные книги (см. выше).
  • --epub-cover-image=cover.jpeg — использовать cover.jpeg как обложку.
  • --toc --toc-depth=2 — построить оглавление, включая заголовки до уровня ##.
  • --split-level=1 — разбивать книгу на отдельные XHTML-файлы по заголовкам уровня # (по одному файлу на главу). Это важно для читалок: они быстрее листают и не подвисают на «одностраничной» книге.
  • --mathml — рендерить математику в MathML (нативно поддерживается современными ридерами).

После запуска pandoc выведет предупреждения и ошибки (если они есть) и создаст файл book_ru.epub. Предупреждения стоит пробежать глазами — обычно это «битые» ссылки на изображения или неоднозначные заголовки.


7. Валидация EPUB

Быстрая проверка структуры

EPUB — это обычный ZIP-архив, поэтому быстро заглянуть внутрь можно стандартным unzip:

unzip -l book_ru.epub | head -30

Пример вывода:

Archive:  book_ru.epub
  Length      Date    Time    Name
---------  ---------- -----   ----
       20  2026-05-23 11:04   mimetype
      252  2026-05-23 11:04   META-INF/container.xml
      161  2026-05-23 11:04   META-INF/com.apple.ibooks.display-options.xml
     6328  2026-05-23 11:04   EPUB/content.opf
     3016  2026-05-23 11:04   EPUB/toc.ncx
     2329  2026-05-23 11:04   EPUB/nav.xhtml
      993  2026-05-23 11:04   EPUB/text/title_page.xhtml
     3778  2026-05-23 11:04   EPUB/styles/stylesheet1.css
    31574  2026-05-23 11:04   EPUB/media/file30.jpg
    20557  2026-05-23 11:04   EPUB/media/file29.jpg
    ...

Проверяем, что архив устроен по спецификации EPUB:

# Должен вывести: application/epub+zip
unzip -p book_ru.epub mimetype

# Точка входа в книгу
unzip -p book_ru.epub META-INF/container.xml

Считаем количество глав (при --split-level=1 это один XHTML на каждый #-заголовок):

unzip -l book_ru.epub | grep -c 'ch[0-9]*\.xhtml'

Проверяем, что обложка и иллюстрации попали внутрь:

unzip -l book_ru.epub | grep -iE 'cover|page_'

Полноценная валидация через epubcheck

epubcheck от W3C — это индустриальный стандарт проверки EPUB-файлов. Для его работы нужен Java:

sudo apt install -y default-jre

Скачиваем последний релиз со страницы https://github.com/w3c/epubcheck/releases:

mkdir -p /opt/book-translate/epubcheck && cd /opt/book-translate/epubcheck
wget https://github.com/w3c/epubcheck/releases/download/v5.3.0/epubcheck-5.3.0.zip
unzip epubcheck-5.3.0.zip

epubcheck-5.3.0.zip

Запускаем проверку:

java -jar /opt/book-translate/epubcheck/epubcheck-5.3.0/epubcheck.jar /opt/book-translate/book_ru.epub

Идеальный вывод выглядит так:

Validating using EPUB version 3.3 rules.
No errors or warnings detected.
Messages: 0 fatals / 0 errors / 0 warnings / 0 infos
EPUBCheck completed

Если есть предупреждения — обычно это либо невалидный HTML внутри глав (исправляется правкой исходного Markdown), либо мелкие проблемы с метаданными.


8. Открываем книгу

Готовый book_ru.epub можно открыть:

  • в Calibre — для удобного просмотра, редактирования метаданных и конвертации в другие форматы (MOBI, AZW3 и т. д.);
  • в любом ридере — Apple Books, KOReader, PocketBook, Foliate (Linux);
  • скопировать на электронную книжку — большинство современных ридеров (Kindle через send-to-kindle, PocketBook, Onyx Boox) понимают EPUB напрямую.

Итоги

В сухом остатке пайплайн получается такой:

  1. PDF → Markdown через marker-pdf (с урезанием служебных страниц через --page_range).
  2. Чистка Markdown парой Python-скриптов (cleanup.py, cleanup2.py).
  3. Глоссарий имён и терминов — извлекается автоматически скриптом build_glossary.py, вычитывается вручную в glossary.yaml.
  4. Перевод локальной LLM через Ollama: чанки по абзацам, контекст предыдущих абзацев, подстановка глоссария в каждый промпт, возобновляемость через translation_state.jsonl.
  5. Постобработка переведённого Markdown: проверка артефактов, заголовков, ссылок на изображения.
  6. Markdown → EPUB через pandoc с метаданными, обложкой и оглавлением.
  7. Валидация результата через epubcheck от W3C.

Что получилось хорошо

  • Полностью локально. Ни PDF, ни перевод не уходят в облако — это важно, если переводите книгу с авторскими правами для личного чтения, или просто не хотите делиться текстом с чужим API.
  • Дёшево. Стоимость — только электричество. У коммерческих API за книгу такого объёма счёт легко улетает в десятки долларов.
  • Воспроизводимо. Скрипты + глоссарий + translation_state.jsonl — при желании можно перезапустить любой этап и получить тот же результат.
  • EPUB на выходе. Это не просто «текстовый файл с переводом», а полноценная книга с обложкой, оглавлением и метаданными, которую приятно читать на ридере.