bloat2md
Превращает документ в markdown, который не стыдно положить в промпт. PDF, docx, xlsx, pptx, OpenDocument, rtf, html, epub, csv, json, yaml, xml, ipynb, картинки и zip — на выходе один markdown, а страницы, на которых текста нет, приезжают пережатыми jpeg.
Пакет — это две половины. bloat2md без экстры ставит только SDK: класс,
который умеет сходить в сервис по HTTP. bloat2md[server] добавляет парсеры и
сам сервис.
Установка
pip install bloat2md
pip install 'bloat2md[server]'
SDK
from bloat2md import Client
client = Client("http://bloat2md:8084", timeout=45.0)
rendered = await client.convert("report.pdf", raw)
if rendered is not None:
print(rendered.markdown)
convert не бросает: сервис лежит, документ не распознан, ответ невнятный —
везде None и запись в лог. Вызывающему остаётся решить, что делать с
документом, который прочитать не вышло, и это обычно «пропустить».
Rendered — это kind, markdown, images (список пар mime/bytes), pages,
truncated и dropped. Последнее — счётчики того, что выкинули по дороге:
hidden_nodes, hidden_spans, hidden_sheets, unsafe_names, bidi,
zero_width и прочее.
Сервис
bloat2md
POST /convert принимает файл как есть, application/octet-stream, имя — в
заголовке X-File-Name. Имя нужно только там, где по содержимому не отличить
csv от txt, а yaml от чего угодно.
| Код | Когда |
|---|---|
| 200 | markdown, картинки, счётчики |
| 413 | файл больше BLOAT2MD_INPUT_LIMIT_MB |
| 415 | это не документ |
| 422 | документ, но прочитать не вышло |
| 504 | воркер не уложился в BLOAT2MD_TIMEOUT |
GET /healthz отвечает {"status": "ok"} и не попадает в трейсы.
Песочница
Документ — чужой ввод, и парсеры у него на пути написаны не нами. Поэтому
конвертация уходит в дочерний процесс: своя сессия, свой TMPDIR, урезанное
окружение и RLIMIT_AS, RLIMIT_CPU, RLIMIT_FSIZE, RLIMIT_CORE. Воркер
общается с сервисом одним JSON через stdout, а по таймауту его группа процессов
получает SIGKILL.
Из этого следует, что MemoryError в парсере стоит одного запроса, а не пода.
Инъекции
Текст, которого человек в документе не увидит, до промпта не доходит: скрытые
css-ом узлы html, невидимый и микроскопический текст в pdf, спрятанные листы
xlsx. Bidi-управляющие символы, теги Unicode, нулевой ширины и длинные цепочки
joiner'ов вырезаются, а сколько чего вырезали — видно в dropped.
Zip не распаковывается, а описывается: имена и размеры. Архив с ratio выше
BLOAT2MD_MAX_ARCHIVE_RATIO отвергается целиком.
Настройки
Переменные окружения с префиксом BLOAT2MD_, читается и .env.
| Переменная | По умолчанию | Что делает |
|---|---|---|
HOST, PORT |
127.0.0.1, 8084 |
что слушать |
ENV |
prod |
окружение для трейсов |
TIMEOUT |
30 |
секунд на документ |
MEMORY_LIMIT_MB |
512 |
RLIMIT_AS воркера |
CPU_SECONDS |
20 |
RLIMIT_CPU воркера |
OUTPUT_LIMIT_MB |
64 |
RLIMIT_FSIZE воркера |
INPUT_LIMIT_MB |
20 |
больше — 413 |
MAX_PAGES |
50 |
страниц pdf читаем |
MAX_RENDER_PAGES |
10 |
страниц отдаём картинкой |
RENDER_EDGE |
1536 |
длинная сторона картинки |
RENDER_QUALITY |
80 |
jpeg |
MAX_MARKDOWN_CHARS |
200000 |
дальше режем с серединой |
MAX_IMAGE_PIXELS |
50000000 |
защита от decompression bomb |
MAX_ARCHIVE_MEMBERS |
2000 |
членов в архиве |
MAX_ARCHIVE_RATIO |
100 |
во сколько раз член вправе распухнуть |
MAX_ARCHIVE_BYTES |
104857600 |
суммарный размер после распаковки |
LIBREOFFICE |
true |
пускать ли soffice |
LibreOffice
Doc, xls, ppt и всё, на чём споткнулись питонячьи парсеры, уходят в
soffice --convert-to pdf, а дальше идут как pdf. Если бинаря нет, формат
просто становится неподдерживаемым — падать по этому поводу незачем.
Образ ghcr.io/wprhvso/bloat2md собирается с libreoffice-*-nogui внутри.
CI
Раннеры здесь ubuntu-latest, а не pool, как в остальных репозиториях.
Флот self-hosted раннеров разворачивается по списку реп в runners.toml, а тот
лежит зашифрованным в кластере — новый репозиторий в него сам собой не попадёт,
и job'ы висели бы в очереди вечно. Репозиторий публичный, GitHub считает его
минуты бесплатными, так что цена нулевая. Появится строка в runners.toml —
можно вернуть pool и убрать шаги установки uv и earthly.
cd собирает и публикует только тогда, когда версии в pyproject.toml ещё нет
тега: PyPI по trusted publishing, образ в ghcr, следом тег vX.Y.Z.
Пин образа по digest в deploy/bloat2md/deployment.yaml CD ставит сам: сразу
после пуша в ghcr он спрашивает у реестра digest тега и коммитит его в main.
Руками digest не резолвится и в манифесте не выдумывается никогда.
Лицензия
MIT
Metadata
Release files for bloat2md 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bloat2md-0.1.0.tar.gz | 105.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bloat2md-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.2 kB
Release files / bloat2md-0.1.0.tar.gz
| Download URL | bloat2md-0.1.0.tar.gz |
|---|---|
| Size | 105.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df4f4499550d563e968baeea62d1984b9c778050f3d2bd302ad25d7faa905299
|
|
BLAKE2b-256 checksum How to use checksums |
b4fe5bfa2ad520dc36afab7309436d0c914729a377a786d52dfd53582e71ba2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bloat2md-0.1.0-py3-none-any.whl
| Download URL | bloat2md-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
acc01be3d8a06028b0f2a2f3783a5f3ded58a046ca5eebe392da01fe2f4c49d6
|
|
BLAKE2b-256 checksum How to use checksums |
a0b74c20cfc3f4a7dd604fee0b519b7e62a92d314f86f56c7f984a73ab8b01e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|