Extract text from office documents and images (PDF, images, DOCX, PPTX, XLSX, and legacy Office via LibreOffice).
Project description
OCR Extractor
Extract text from office documents and images. PDF and image files are
processed via OCR (Tesseract + OpenCV). Modern Office files
(.docx/.pptx/.xlsx) are read directly. Legacy Office files
(.doc/.xls/.ppt) are converted via LibreOffice headless.
ocr-extractor is distributed as an installable Python package. You
can use it as a dependency in other projects (pip install ocr-extractor),
run it from the command line (ocr-extractor file.pdf), or import it as a
library (from ocr_extractor import read_document).
📋 Table of contents
- Features
- Requirements
- Installation
- Usage
- Project structure
- Code description
- Output
- Customization
- Troubleshooting
- Publishing the package
- License
✨ Features
- Multi-format support: PDF, images (PNG/JPG/TIFF/BMP/WEBP/HEIC),
modern Office (DOCX/PPTX/XLSX), and legacy Office (DOC/XLS/PPT via
LibreOffice) — all through a single
read_documententry point. - PDF → Image conversion: each page of the PDF is rendered to a
high-resolution image (300 DPI by default) using
pdf2image. - Image preprocessing: grayscale conversion and noise removal with
OpenCV(fastNlMeansDenoising) to improve OCR accuracy. - Multi-page TIFF support: each frame of a multi-page TIFF becomes
its own
=== PAGE N ===block, useful for scanner output. - Tesseract OCR: text extraction via
pytesseractwith a configurable language (engby default). - Text cleaning: filtering of short lines, disallowed characters, and symbol-only lines (applied only to OCR-based formats).
- Page markers: PDF, TIFF, PPTX, and XLSX output is wrapped between
=== PAGE N ===/=== END PAGE N ===markers so each page/slide/sheet is identifiable. - Public API + CLI: installable as a dependency, invokable as the
ocr-extractorcommand, or importable as a Python library.
🛠 Requirements
- Python 3.8+
- Tesseract OCR installed on the system (not installed via
pip):# Debian / Ubuntu sudo apt-get install tesseract-ocr # Fedora sudo dnf install tesseract # macOS (Homebrew) brew install tesseract
- Poppler (required by
pdf2image):# Debian / Ubuntu sudo apt-get install poppler-utils # Fedora sudo dnf install poppler-utils # macOS brew install poppler
- LibreOffice — required only for legacy Office files (
.doc,.xls,.ppt):# Debian / Ubuntu sudo apt-get install libreoffice # Fedora sudo dnf install libreoffice # macOS brew install --cask libreoffice
- Tesseract language packs (optional). The default is
eng; install others as needed, for example:sudo apt-get install tesseract-ocr-spa # Spanish sudo apt-get install tesseract-ocr-deu # German
- pillow-heif (optional) — required for HEIC/HEIF images from iPhones:
pip install ocr-extractor[heic]
📦 Installation
As a dependency in another project
Once published to PyPI (or your private index):
pip install ocr-extractor
This installs the library and puts the ocr-extractor command on your
PATH. Python dependencies are resolved automatically (Tesseract,
Poppler, and LibreOffice remain system-level requirements).
To pin or constrain the version in pyproject.toml / requirements.txt:
# requirements.txt
ocr-extractor>=0.2.0
For HEIC/HEIF support (iPhone photos), install the [heic] extra:
pip install ocr-extractor[heic]
Editable install from the repository
Recommended during development. Clone the repo and, inside the virtual environment where you want to use it:
git clone https://github.com/roilanrodriguez55/ocr-extractor.git
cd ocr-extractor
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pip install -e . registers the package in editable mode: any change in
ocr_extractor/ is picked up without reinstalling. The dev extra adds
pytest and build for tests and for producing sdist/wheel artifacts.
Dependencies only (without cloning)
If you don't want to install this project and only need the API from your own code, install the Python dependencies directly:
pip install pytesseract pdf2image opencv-python-headless numpy Pillow \
python-docx python-pptx openpyxl
💡 If your platform supports it, you can also use
opencv-pythoninstead ofopencv-python-headless(the headless variant avoids GUI dependencies).
Usage
There are three ways to invoke the OCR, in order of recommendation:
From the CLI
After pip install ocr-extractor (or pip install -e .):
# Basic usage: writes texto_limpio.txt next to the input
ocr-extractor file.pdf
ocr-extractor scan.tiff # multi-page TIFF
ocr-extractor report.docx # no OCR — direct text extraction
ocr-extractor presentation.pptx # one marker block per slide
ocr-extractor spreadsheet.xlsx # one marker block per sheet
# Customize output, resolution, and language (OCR formats only)
ocr-extractor file.pdf -o output.txt --dpi 300 --lang spa
# Quiet mode (no progress messages)
ocr-extractor file.pdf -q
# Help and version
ocr-extractor --help
ocr-extractor --version
Available arguments:
| Argument | Description | Default |
|---|---|---|
input |
Path to the input file (positional, required) | — |
-o, --output |
Path to the output text file | texto_limpio.txt |
--dpi |
Rendering resolution in DPI (OCR formats only) | 300 |
--lang |
Tesseract language code (eng, spa, …) |
eng |
-q, --quiet |
Suppress progress messages | off |
--version |
Print version and exit | — |
As a library (API)
from ocr_extractor import read_document
# Format is detected by extension. Same call works for any supported format.
text = read_document("file.pdf", dpi=300, lang="eng")
text = read_document("scan.tiff")
text = read_document("report.docx")
text = read_document("presentation.pptx")
text = read_document("spreadsheet.xlsx")
text = read_document("legacy.doc") # requires LibreOffice
# You can also use the intermediate steps directly
from ocr_extractor import preprocess_image, clean_text
from pdf2image import convert_from_path
pages = convert_from_path("file.pdf", dpi=300)
gray = preprocess_image(pages[0]) # numpy.ndarray ready for OCR
Functions exposed from ocr_extractor:
read_document(path, *, dpi=300, lang="eng", verbose=True) -> strpreprocess_image(image_pil) -> numpy.ndarrayclean_line(line) -> str | Noneclean_text(text) -> strread_pdf(pdf_path, dpi=300, lang="eng", verbose=True) -> str(deprecated, will be removed in the next major release)SUPPORTED_FORMATS— tuple of all supported extensions__version__— string, e.g."0.2.0"
Backward-compatible app.py wrapper
app.py is kept for backward compatibility: instead of duplicating logic,
it now delegates to the package. It still works as before:
python app.py file.pdf -o output.txt
Internally it calls ocr_extractor.cli.main, so it accepts the same
arguments as the CLI (run ocr-extractor --help to see them all).
📁 Project structure
ocr-extractor/
├── pyproject.toml # Metadata, deps, entry point (built via hatchling)
├── MANIFEST.in # Includes README.md in the sdist
├── .gitignore # Standard Python ignores
├── README.md # This file
├── SUPPORTED_FORMATS.md # Format inventory with priorities
├── app.py # Thin wrapper that delegates to ocr_extractor.cli
└── ocr_extractor/ # Python package (what gets distributed)
├── __init__.py # Public API + __version__ + SUPPORTED_FORMATS
├── core.py # preprocess_image, clean_line, clean_text, deprecated read_pdf
├── dispatcher.py # read_document (format detection + routing)
├── cli.py # argparse + main() (CLI entry point)
└── readers/ # One module per format family
├── __init__.py # EXTENSION_READERS map + get_reader_name
├── pdf.py # OCR via pdf2image + Tesseract
├── images.py # OCR for PNG/JPG/BMP/WEBP/HEIC (single page) and TIFF (multi-page)
├── docx.py # python-docx: paragraphs + tables
├── pptx.py # python-pptx: shapes + tables
├── xlsx.py # openpyxl: worksheets as pages
└── legacy.py # .doc/.xls/.ppt → LibreOffice → modern reader
🔍 Code description
ocr_extractor.dispatcher.read_document(path, *, dpi=300, lang="eng", verbose=True)
Format-agnostic entry point. Detects the file extension, picks the
right reader from ocr_extractor.readers.EXTENSION_READERS, and
returns its output. Raises FileNotFoundError for missing files and
ValueError for unsupported extensions.
ocr_extractor.core.preprocess_image(image_pil)
Converts a PIL image to an OpenCV array, turns it grayscale, and applies
a denoising filter (cv2.fastNlMeansDenoising) with the parameters
h=10, templateWindowSize=7, and searchWindowSize=21. Returns the
grayscale image ready for OCR.
ocr_extractor.core.clean_line(line)
Cleans a single text line:
- Discards lines shorter than 2 characters.
- Replaces any character not in
a-zA-Z0-9 '-.!?with a space (note: the comma is not allowed — see "Customization" below). - Collapses multiple spaces into one.
- Discards lines that contain only symbols (
()[]{}_-=+*#@^.,:;<>/\|~).
ocr_extractor.core.clean_text(text)
Applies clean_line to every line of the text and keeps only the lines
that pass the filter. Returns the cleaned text joined with newlines.
ocr_extractor.readers.pdf.read_pdf_pages(pdf_path, dpi=300, lang="eng", verbose=True)
Iterates over every page of the PDF:
- Renders each page to an image with
convert_from_path(pdf_path, dpi=dpi). - Preprocesses the image with
preprocess_image. - Extracts the text with
pytesseract.image_to_string(..., lang=lang). - Cleans the text with
clean_text. - Wraps the result between the markers
=== PAGE N ===…=== END PAGE N ===.
ocr_extractor.readers.images.read_image(path, *, dpi, lang, verbose)
Opens a single-page image (PNG, JPG, BMP, WEBP, HEIC) with Pillow,
runs it through preprocess_image and pytesseract.image_to_string,
and returns the cleaned text without page markers. HEIC requires
pillow-heif (install via pip install ocr-extractor[heic]).
ocr_extractor.readers.images.read_tiff(path, *, dpi, lang, verbose)
Iterates over every frame of a multi-page TIFF. Each frame is OCR'd
and wrapped in === PAGE N === / === END PAGE N === markers, just
like the PDF reader.
ocr_extractor.readers.docx.read_docx(path, *, dpi, lang, verbose)
Walks the document body in order, emitting one line per non-empty
paragraph and one line per table row (cells joined with |). DOCX
files are not paginated, so no page markers are emitted.
ocr_extractor.readers.pptx.read_pptx(path, *, dpi, lang, verbose)
Iterates over slides, extracting text from every shape that has a
text_frame and from tables. Each slide becomes one === PAGE N ===
marker block.
ocr_extractor.readers.xlsx.read_xlsx(path, *, dpi, lang, verbose)
Opens the workbook with openpyxl (read_only=True, data_only=True),
iterates every worksheet, and emits one === PAGE <sheet name> ===
block per sheet. Rows become | col1 | col2 | col3.
ocr_extractor.readers.legacy.read_legacy_office(path, *, dpi, lang, verbose)
Shells out to soffice --headless --convert-to <modern> to convert
.doc/.xls/.ppt to .docx/.xlsx/.pptx, then delegates to the
corresponding modern reader. Raises RuntimeError with installation
instructions if LibreOffice is not installed on the system.
ocr_extractor.cli.main(argv=None)
Entry point registered in pyproject.toml as
ocr-extractor = "ocr_extractor.cli:main". Parses arguments with
argparse, validates that the input file exists, calls read_document,
and writes the result to the output file. All errors are caught at the
top level; the process exits with 0 on success, 1 on error.
app.py (wrapper)
A few lines that import ocr_extractor.cli.main and call it when the
file is executed. It exists only to avoid breaking callers that still
run python app.py file.pdf.
📤 Output
texto_limpio.txt (or the file passed with -o) contains the extracted
text with this structure:
=== PAGE 1 ===
<cleaned text of page 1>
=== END PAGE 1 ===
=== PAGE 2 ===
<cleaned text of page 2>
=== END PAGE 2 ===
ℹ️ OCR quality depends on the PDF's resolution, the typeface used, and the presence of images or watermarks. Low-resolution scanned documents will produce more recognition errors.
⚙️ Customization
- Change input / output / language / DPI: use the CLI arguments
(
-o,--lang,--dpi) or theread_documentparameters from the API:from ocr_extractor import read_document text = read_document("file.pdf", dpi=400, lang="spa")
- Adjust the resolution: higher
dpi(400–600) gives better results on low-quality documents, at the cost of more memory and time. - Loosen the character filter: extend the
allowedset insideocr_extractor.core.clean_lineto allow additional characters (for example,,áéíóúÁÉÍÓÚñÑ¿¡). Since it lives insideocr_extractor/, rerunpip install -e .after a change so the installed CLI picks it up.
🧪 Troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
ModuleNotFoundError: No module named 'ocr_extractor' |
The package isn't installed in the active Python | pip install -e . (editable) or pip install ocr-extractor |
ocr-extractor: command not found |
The PATH doesn't include the venv's scripts |
Activate the virtualenv or use python -m ocr_extractor.cli |
TesseractNotFoundError |
The Tesseract binary is not on the PATH |
Install Tesseract or set pytesseract.pytesseract.tesseract_cmd to the absolute path of the binary |
pdf2image fails with a Poppler error |
Poppler is not installed | Install poppler-utils (Linux) or brew install poppler (macOS) |
| Extracted text is empty | Image-based PDF with very low resolution | Raise --dpi or improve the source PDF |
| Accented characters / ñ are lost | clean_line only allows ASCII |
Extend the allowed set with áéíóúÁÉÍÓÚñÑ¿¡ |
unsupported file extension '.xyz' |
The format isn't supported | Check ocr_extractor.SUPPORTED_FORMATS for the supported list |
LibreOffice error on .doc/.xls/.ppt |
soffice is not on the PATH |
Install LibreOffice (see Requirements) |
cannot identify image file '.heic' |
pillow-heif is not installed |
pip install ocr-extractor[heic] |
DeprecationWarning: ocr_extractor.read_pdf is deprecated |
Code still imports the old read_pdf |
Switch to from ocr_extractor import read_document |
Encoding errors in the .txt |
File opened without UTF-8 | The CLI uses encoding="utf-8"; if you write manually, use the same encoding |
| Slow on long PDFs | 300 DPI rendering + per-page OCR | Lower --dpi or process the PDF in batches |
🚀 Publishing the package
To build an sdist and a wheel locally (requires the dev extra):
pip install -e ".[dev]"
python -m build
ls dist/ # should show ocr_extractor-0.1.0.tar.gz and *.whl
To publish to PyPI (use the token you have in ~/.pypirc or env vars):
python -m pip install --upgrade twine
python -m twine upload dist/*
⚠️ Before publishing, add a
LICENSEfile and fill in thelicenseandauthorsfields inpyproject.toml; PyPI requires them.
📝 License
This project is distributed for educational purposes. No LICENSE file
is included at the moment. Feel free to adapt it to your needs; if you
plan to redistribute it, add an explicit license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ocr_extractor-0.3.1.tar.gz.
File metadata
- Download URL: ocr_extractor-0.3.1.tar.gz
- Upload date:
- Size: 30.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf6a6cb2078c95de39babd6c4efd8bca42f127ca3efd70fdf0c096596cb0b4fd
|
|
| MD5 |
a1241a927434b8b5941389dc66a0e4a6
|
|
| BLAKE2b-256 |
87099082413c9ec6b9934f55376fbb3ab22b6a928ec5ee576394c8205e0b6a77
|
File details
Details for the file ocr_extractor-0.3.1-py3-none-any.whl.
File metadata
- Download URL: ocr_extractor-0.3.1-py3-none-any.whl
- Upload date:
- Size: 22.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf3dd300a9c9708faa365425a2b62a6cb559b2353bcb5a09bc714c083f45cfa4
|
|
| MD5 |
42d983f0e4cb2e576583541cea874b4f
|
|
| BLAKE2b-256 |
4e7c6d1b17155440b2547c032706893596bac9df145401bf94bc7d90d102e98e
|