pydown converts common file formats into clean Markdown. Hand it a file path,
get back a Markdown string. It's a from-scratch, well-tested take on what tools
like Microsoft's Markitdown do, built
around two small OOP patterns (Template Method + Strategy).
| Format | Library | What gets extracted |
|---|---|---|
pymupdf |
Text per page, under ## Page N headers |
|
| DOCX | python-docx |
Headings, paragraphs, and tables (in order) |
| PPTX | python-pptx |
Slide titles, body text, and speaker notes |
| XLSX | openpyxl |
Each sheet as a Markdown table |
| PNG/JPG/JPEG | pytesseract |
OCR'd text from the image |
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
brew install tesseract # macOS — only needed for image OCRtesseract is a system dependency (not a Python package). Image conversion needs
it; every other format works without it.
import pydown
markdown = pydown.convert("report.pdf")
print(markdown)convert() picks the right converter from the file extension. It raises:
ValueErrorfor an unsupported extension, andFileNotFoundErrorif the file doesn't exist.
Input file
│
▼
core.py Strategy: extension → converter class lookup
│
▼
BaseConverter Template Method: validates the path + extension once,
│ then delegates to the subclass
▼
_convert() Format-specific extraction (one method per converter)
│
▼
Markdown string
BaseConverter.convert() does all the shared validation, so each converter only
implements _convert(). Adding a format means writing one converter class and
adding one line to the CONVERTERS dict in pydown/core.py.
Every test generates its own fixture files at runtime (no sample files are committed), so the suite runs anywhere:
source venv/bin/activate
python -m pytestThe image/OCR tests are skipped automatically when the tesseract binary isn't
installed.
A drag-and-drop web frontend wraps the library: drop a file, a .md downloads.
source venv/bin/activate
python -m pydown.web.appThen open http://127.0.0.1:5050. (It runs on 5050 because macOS's AirPlay
Receiver occupies port 5000. Override with PORT=8080 python -m pydown.web.app.)
The app is containerized so it can run anywhere — including image OCR, which needs
the tesseract system binary baked into the image (this is why a plain serverless
host like Vercel won't work). Build and run it locally with Docker:
docker build -t pydown .
docker run --rm -p 8080:8080 -e PORT=8080 pydown
# → http://localhost:8080To put it online, deploy the same Dockerfile to any container host:
- Render — push this repo to GitHub, then New → Web Service → Build from a
Dockerfile. Render supplies
$PORTand serves it athttps://<name>.onrender.com. - Fly.io / Railway — deploy the identical
Dockerfile(Fly does it from your machine viaflyctl launch/flyctl deploy, no GitHub required).
Uploads are capped at 25 MB (MAX_CONTENT_LENGTH in
pydown/web/app.py).