Skip to main content

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bloat2md-0.1.0.tar.gz (105.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bloat2md-0.1.0-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

Details for the file bloat2md-0.1.0.tar.gz.

File metadata

  • Download URL: bloat2md-0.1.0.tar.gz
  • Upload date:
  • Size: 105.5 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for bloat2md-0.1.0.tar.gz
Algorithm Hash digest
SHA256 df4f4499550d563e968baeea62d1984b9c778050f3d2bd302ad25d7faa905299
MD5 40c4efd38d0b0c2df5477b604ff0270e
BLAKE2b-256 b4fe5bfa2ad520dc36afab7309436d0c914729a377a786d52dfd53582e71ba2e

See more details on using hashes here.

File details

Details for the file bloat2md-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bloat2md-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.7 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for bloat2md-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 acc01be3d8a06028b0f2a2f3783a5f3ded58a046ca5eebe392da01fe2f4c49d6
MD5 511229cb72458cb525b127add0b354a3
BLAKE2b-256 a0b74c20cfc3f4a7dd604fee0b519b7e62a92d314f86f56c7f984a73ab8b01e6

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page