Skip to main content

Convert documents to LLM-ready markdown. Ruled-table-aware PDF extraction, offline, no cloud.

Project description

TrimItDown

Any document → clean, LLM-ready Markdown. On your own server, on every device you own.

Read this in: English (this file) · русский

Latest release Downloads macOS build PyPI License: MIT Platforms

TrimItDown converting a PDF: live Markdown preview with a real table and a token counter

TrimItDown turns PDF, Word (docx), PowerPoint (pptx), Excel (xlsx/xls) and Outlook (.msg) files into clean, readable Markdown — ready to paste into Claude/ChatGPT, Obsidian, Notion, or any markdown vault. It runs as an iPhone/iPad app (installed straight from Safari, no App Store), a portable Windows program, a macOS app, and a self-hosted Docker server — with one shared archive of conversions across all your devices.

The PDF engine

Most converters — including the stock MarkItDown PDF path — stumble on real-world PDFs in three measurable ways: they glue words together, invent tables out of ordinary prose, and drop genuine ruled tables. Measured over 891 real documents from two independent collections, the stock converter emitted 5,624 table rows on documents containing no ruled grid at all — prose reshaped into tables — against 2 from the engine below. On the 885 of those documents that are not broken at the source, it roughly halves glued words (107 runs vs 53). Those corpora are third-party copyrighted material and are not redistributable — the full method, the numbers, the cases where this engine loses, and what a reader can reproduce are in docs/pdf-engine.md.

So TrimItDown replaced it with a custom extraction engine:

  • Words split at a measured word-gap threshold — a fraction of the font size, not a fixed point value, so it holds across small print and large headings alike.
  • A dedicated table-detection stage validates every candidate grid by how its cells are actually filled (a row-fill vote), instead of trusting every ruled rectangle. Diagrams and decorative frames get rejected; their text flows back into prose instead of vanishing.
  • Genuine ruled tables render as honest Markdown tables, cell for cell.

Here is one document converted both ways. The file is committed to this repo, so you can run the comparison yourself:

python scripts/compare_pdf_engines.py tests/data/sample-service-report.pdf

Stock converter — a phantom empty column splits the data, shifting every value one column right of its header:

| Parameter                 | Unit | Before service |     | After service | Limit |
| ------------------------- | ---- | -------------- | --- | ------------- | ----- |
| Supply airflow            | m³/h |                | 352 | 398           | ≥ 380 |
| Extract airflow           | m³/h |                | 341 | 402           | ≥ 380 |
| Filter pressure drop      | Pa   |                | 184 | 92            | ≤ 150 |
| Heat recovery efficiency  | %    |                | 61  | 78            | ≥ 70  |
| Motor current, supply fan | A    |                | 1.9 | 1.6           | ≤ 2.2 |

TrimItDown — the table as it appears on the page:

| Parameter | Unit | Before service | After service | Limit |
| --- | --- | --- | --- | --- |
| Supply airflow | m³/h | 352 | 398 | ≥ 380 |
| Extract airflow | m³/h | 341 | 402 | ≥ 380 |
| Filter pressure drop | Pa | 184 | 92 | ≤ 150 |
| Heat recovery efficiency | % | 61 | 78 | ≥ 70 |
| Motor current, supply fan | A | 1.9 | 1.6 | ≤ 2.2 |

One sample proves nothing about a corpus, and ours is third-party material we cannot publish. So the measurement ships instead of the documents — point it at your own PDFs and it runs both converters over all of them and prints a comparable summary, counts only, no filenames:

python scripts/measure_corpus.py /path/to/your/pdfs --limit 50

Please send that summary back whatever it says — a win, a loss, or a tie (issue template). Results collected only when something breaks are a sample made of breakage, and nothing honest can be concluded from it. Pooled across enough corpora, these numbers stop being one person's collection. Details in docs/pdf-engine.md.

In short: we add the table-validation stage that the classic tabula-java pipeline has and Python extractors lack, expressed as a cell-fill vote on pdfplumber's ruled grids, with per-grid fallback to prose — no ML models, no cloud, small enough to ship inside a portable binary. Every non-PDF format still goes through MarkItDown.

Why TrimItDown

  • Made for the LLM workflow. Clean Markdown out, a live preview, and a token counter that shows what a document will cost before you paste it into a model's context.
  • Your files never leave your infrastructure. Conversion happens on your own server (home NAS, VPS) or fully offline on your computer. No third-party SaaS, no per-page fees.
  • One archive, every device. Convert on your phone — the result is already on your computer, and vice versa. Searchable, with batch conversion and ZIP export.
  • A real app experience everywhere. iPhone PWA installed from Safari (no App Store), a portable Windows exe, a macOS app. Russian and English UI, light/dark, two color themes.

Get it

Platform How
Windows Download TrimItDown-windows-x64.exe from Releases — portable, no install
macOS (Apple Silicon / Intel) Download the matching .zip from Releases, unzip, right-click → Open
iPhone / iPad Served by your own Docker server — open it in Safari → Share → Add to Home Screen
Docker server See Self-hosting below

The desktop apps work fully offline out of the box. Point them at your server in Settings to get the shared archive.

Command-line package

Run a conversion without installing anything permanently:

uvx trimitdown convert report.pdf > report.md

Or install it with pip install trimitdown and write directly to a file with trimitdown convert report.pdf -o report.md. Without -o, Markdown goes to standard output. The CLI works fully offline.

Self-hosting

The Docker server is the source of truth: it converts, stores the shared archive, and serves the iPhone PWA.

git clone https://github.com/serjdrej/trimitdown.git
cd trimitdown/docker-server
# one-time: generate a self-signed HTTPS certificate (copy-paste command in docker-server/README.md)
docker-compose up -d --build

Then open https://YOUR_SERVER:8002. Full instructions — certificate generation, trusting it on iOS/Windows/macOS, and the API — in docker-server/README.en.md.

Screenshots

Archive, shared across devices Settings
Archive with search Settings screen
Ocean theme, light Ocean theme, dark
Ocean light theme Ocean dark theme
The iPhone PWA — home screen Converting a PDF
TrimItDown PWA on iPhone, empty state TrimItDown PWA on iPhone, conversion result

How it works

  • The Docker server is a FastAPI service with HTTPS; the archive lives on the server. The same service serves the PWA for iPhone.
  • The Windows/macOS apps check at startup whether your server is reachable: if yes, they open straight on it (shared archive); if not, they spin up a bundled local server and work fully offline. The shell is pywebview (WebView2 / WKWebView), packaged with PyInstaller.
  • Conversion: PDFs go through TrimItDown's own engine (trimitdown-pdf, built on pdfplumber); every other format goes through Microsoft's MarkItDown.

Repository layout

Contributing

Bug reports and PRs are welcome — see CONTRIBUTING.md. DEVELOPMENT.md documents the dev setup and the desktop build internals.

Limitations

  • The server's self-signed HTTPS certificate needs a one-time manual trust on each device (iOS: profile + full trust; Windows: import into CurrentUser\Root; macOS: Keychain).
  • The binaries aren't code-signed (no Apple/Microsoft developer certificate) — first launch needs a one-time override (macOS: right-click → Open; Windows: SmartScreen).

License and credits

Code is MIT. Non-PDF conversion is powered by MarkItDown (Microsoft, MIT); the bundled third-party licenses are listed in THIRD_PARTY_NOTICES.md.

Project details


Download files

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

Source Distribution

trimitdown-0.1.0.tar.gz (795.1 kB view details)

Uploaded Source

Built Distribution

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

trimitdown-0.1.0-py3-none-any.whl (790.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: trimitdown-0.1.0.tar.gz
  • Upload date:
  • Size: 795.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for trimitdown-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e114c1b77340687343bddd7e6109597b98adfc311af6539a6690243d7a842edd
MD5 142505a7efb762b8b209c030afda8870
BLAKE2b-256 fca25714bfa81edd1fac2d33f4e82c11141af1b7aa3b7e9f4de8f669434c442c

See more details on using hashes here.

Provenance

The following attestation bundles were made for trimitdown-0.1.0.tar.gz:

Publisher: publish-pypi.yml on serjdrej/trimitdown

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

  • Download URL: trimitdown-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 790.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for trimitdown-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ab2ebed4bff8f20529dd258b3e985df7889f800263c8ce2d17fb8b2eea4cab2
MD5 2ef94ab827e4a8b2030f477ba0019bcd
BLAKE2b-256 620ccfd9211aab9d6e4148b1d4097cb7a4040f9d683c1665084ad3a5b8d1235c

See more details on using hashes here.

Provenance

The following attestation bundles were made for trimitdown-0.1.0-py3-none-any.whl:

Publisher: publish-pypi.yml on serjdrej/trimitdown

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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